﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using LucyGrid.Interface;

namespace LucyGrid
{
	/// <summary>
	/// Manages assemblies and dependencies
	/// </summary>
	public class LucyBinManager
	{
		/// <summary>
		/// Assemblies and resources content cache
		/// </summary>
		static Dictionary<string, byte[]> _binImages;
		
		/// <summary>
		/// Represents a grid master node
		/// </summary>
		public static ILucyMaster imaster;

		static LucyBinManager()
		{
			_binImages = null;
			AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
		}

		/// <summary>
		/// Needed by the binary deserializer to work on the distributed code
		/// For some unknown reason BinaryFormatter fails resolving assemblies
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		/// <returns></returns>
		static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
		{
			GridUtils.NotifyLog("BINMANAGER,INFO", "CurrentDomain_AssemblyResolve {0}", args.Name);
			return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.Equals(args.Name)) ??
				// Try loading from Master
				LoadAssembliyFromMaster(args.Name);

		}

		public static Assembly LoadAssembliyFromMaster(string fullname)
		{
			GridUtils.NotifyLog("BINMANAGER,INFO", "LoadAssembliyFromMaster {0}", fullname);
			byte[] assData = imaster.Assembly(fullname);
			return Assembly.Load(assData);
		}

		public static Dictionary<string, byte[]> GetBinaries(IEnumerable<string> optinalPath )
		{
			if(_binImages == null)
				LoadBinImages(optinalPath);
			return _binImages;
		}

		/// <summary>
		/// Returns an assembly data
		/// </summary>
		/// <param name="fullname"></param>
		/// <returns></returns>
		public static byte[] GetAssemblyBinary(string fullname)
		{
			if (!_binImages.ContainsKey(fullname))
			{
				_binImages.Add(fullname, ReadAllBytes(FindAssembly(fullname).Location));
			}
			return _binImages[fullname];
		}

		/// <summary>
		/// Finds a loaded assembly
		/// </summary>
		/// <param name="fullname"></param>
		/// <returns></returns>
		public static Assembly FindAssembly(string fullname)
		{
			return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.Equals(fullname));
		}

		/// <summary>
		/// Loads in memory the bin image
		/// </summary>
		public static void LoadBinImages(IEnumerable<string> path)
		{
			var basePath = BinPath();
			_binImages = 
				(path ?? new String[0]).Concat(new String[]{basePath})
					.SelectMany(d => Directory.GetFiles(d, "*.*", SearchOption.AllDirectories)
						.Select(f => new { File = f, BasePath = d})
					)
				// Uff...
				.GroupBy(f => Path.GetFileName(f.File))
				.Select(g => g.First())
				.AsParallel()
				.Select(f =>
				{
					try
					{
						var res = new { File = f.File.Replace(f.BasePath, string.Empty), Data = ReadAllBytes(f.File) };
						if (res.Data.Length == 0)
							throw new Exception("Zero size length for " + f);
						return res;
					}
					catch (Exception ex)
					{
						GridUtils.NotifyLog("BINMANAGER,INFO", "Error loading bin image file {0}: {1}", f, ex);
					}
					return null;
				})
				.Where(f => f != null)
				.ToDictionary(f => Path.GetFileName(f.File) , f => f.Data);
		}

		/// <summary>
		/// Returns file content
		/// </summary>
		/// <param name="f"></param>
		/// <returns></returns>
		public static byte[] ReadAllBytes(string f)
		{
			// Check if it'a already loaded
			using (Stream s = File.Open(f, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
			{
				using (var ms = new MemoryStream())
				{
					s.CopyTo(ms);
					ms.Seek(0, SeekOrigin.Begin);
					return ms.ToArray();
				}
			}
		}

		/// <summary>
		/// Returns execution binary path
		/// </summary>
		/// <returns></returns>
		public static string BinPath()
		{
			return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		}

		/// <summary>
		/// Returns execution binary fullpath
		/// </summary>
		/// <returns></returns>
		public static string BinFile()
		{
			// WTF: vshost, supposed to be affecting only while working with VS...but let's do some
			// defensive programming :) 
			return Assembly.GetEntryAssembly().Location.Replace(".vshost", string.Empty);
		}

		/// <summary>
		/// Creates an instance of th eobject
		/// </summary>
		/// <param name="executorType"></param>
		/// <returns></returns>
		public static object CreateInstance(Type executorType)
		{
			if (executorType.GetConstructors().Any(c => c.GetParameters().Length == 0 && c.IsPublic))
				return Activator.CreateInstance(executorType);
			return FormatterServices.GetUninitializedObject(executorType);
		}


		/// <summary>
		/// Returns a list of assemblies loaded from files
		/// </summary>
		/// <param name="files"></param>
		/// <returns></returns>
		public static IEnumerable<Assembly> GetAssembliesFromFiles(string[] files)
		{
			return files.Select(file =>
			{
				try
				{
					var asm = Assembly.LoadFile(file);
					GridUtils.NotifyLog("BINMANAGER,INFO", "Assebly loaded {0}", asm.FullName);
					return asm;
				}
				catch (Exception ex)
				{
					GridUtils.NotifyLog("BINMANAGER,ERROR", "Assebly loading error {0}: {1}", file, ex);
					return null;
				}
			})
			.Where(a => a != null)
			.ToArray();
		}
	}
}
